package com.xxmw.transaction.common.utils.bourse;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.xxmw.transaction.common.excep.service.TransactionException;
import com.xxmw.transaction.common.utils.HmacUtil;
import com.xxmw.transaction.common.utils.http.HttpClientUtil;
import com.xxmw.transaction.enumeration.BourseEnum;
import com.xxmw.transaction.enumeration.TradeTypeEnum;
import com.xxmw.transaction.model.app.BourseApi;
import com.xxmw.transaction.model.app.BourseOrder;
import com.xxmw.transaction.model.app.BoursePrice;
import com.xxmw.transaction.model.app.Symbol;
import com.xxmw.transaction.vo.BalanceVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.CollectionUtils;

import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.net.URLEncoder;
import java.util.*;

/**
 * https://github.com/githubdev2020/API_Doc_zh-CN/wiki
 * 币夫工具类
 * @author luowenliang
 * @since 2020/2/5 15:42
 */
@Slf4j
public class BitforexUtil {


    private static final String BASE_URL = "https://api.bitforex.com";

    /**
     * 下单接口
     */
    private static final String ORDER_URL = "/api/v1/trade/placeOrder";

    /**
     * 批量下单接口
     */
    private static final String BATCH_ORDER_URL = "/api/v1/trade/placeMultiOrder";

    /**
     * 获取订单详情
     */
    private static final String ORDER_INFO_URL = "/api/v1/trade/orderInfo";

    /**
     * 撤销订单
     */
    private static final String ORDER_CANCEL_PATH = "/api/v1/trade/cancelOrder";


    /**
     * 查询账号余额
     */
    private static final String ACCOUNT_INFO_URL = "/api/v1/fund/mainAccount";

    /**
     * 深度信息获取
     */
    private static final String DEPTH_PATH = "/api/v1/market/depth";

    /**
     * 交易对前缀
     */
    private static final String SYMBOL_PREFIX = "coin-usdt-";

    private static final String ACCOUNT_REDIS_KEY = "USDT-4";


    public static void main(String[] args) {
        BourseApi api = new BourseApi();
        api.setAccessKey("896d9bb82bc708dfd4a29b093c80926b");
        api.setSecretKey("4ee9756a5c50c613a13446ff95703b27");
        getBalance(api, "usdt");
    }
    /**
     * 账户余额
     * @param api
     */
    public static BalanceVo getBalance (BourseApi api, String currency) {
        JSONObject data = balanceObject(api, currency);
        BalanceVo balanceVo = new BalanceVo();
        balanceVo.setTotal(data.getBigDecimal("fix"));
        balanceVo.setFrozen(data.getBigDecimal("frozen"));
        balanceVo.setAvailable(data.getBigDecimal("active"));
        return balanceVo;
    }

    public static Map<String, BalanceVo> getBalance (BourseApi api, List<String> currency) {
        Map<String, BalanceVo> map = new HashMap<>(16);
        if (currency.size() <= 0) {
            return map;
        }
        for (String s : currency) {
            map.put(s, getBalance(api, s));
        }
        return map;
    }

    public static JSONObject balanceObject (BourseApi api, String currency) {
        TreeMap<String, Object> map = new TreeMap<>();
        map.put("currency", currency);
        map.put("nonce",System.currentTimeMillis());
        map.put("accessKey",api.getAccessKey());
        String sign = sign(api, ACCOUNT_INFO_URL, map);
        StringBuilder params = new StringBuilder();
        map.put("signData",sign);
        map.entrySet().forEach(entry -> params.append(entry.toString()).append("&"));
        String paramStr = params.substring(0, params.length() - 1);
        String res = HttpClientUtil.post(BASE_URL + ACCOUNT_INFO_URL + "?" + paramStr);
        log.info("币夫查询账户余额返回信息: {}", res);
        JSONObject object = JSONObject.parseObject(res);
        if (object.getBoolean("success")) {
            JSONObject data = object.getJSONObject("data");
            return data;
        }
        throw new TransactionException("Bitforex获取账户余额异常");
    }


    /**
     * 币夫下单接口
     * @return
     */
    public static void order (BourseApi api, BourseOrder order) {
        TreeMap<String, Object> map = new TreeMap<>();
        map.put("symbol", BourseEnum.BITFOREX.getSymbolFormat().symbolFormat(order.getBaseCurrency(), order.getQuoteCurrency()));
        map.put("amount",order.getNum());
        map.put("tradeType",(order.getTradeType() == 1 || order.getTradeType()== 3) ? 1 : 2);
        //如果类型是市价买  金额上浮  市价卖 金额下浮
        if (order.getTradeType().equals(TradeTypeEnum.BUY_MARKET.getValue())) {
            BigDecimal price = order.getPrice().multiply(new BigDecimal("1.01"));
            order.setPrice(price);
            order.setTotalPrice(price.multiply(order.getNum()));
        } else if (order.getTradeType().equals(TradeTypeEnum.SELL_MARKET.getValue())) {
            BigDecimal price = order.getPrice().multiply(new BigDecimal("0.99"));
            order.setPrice(price);
            order.setTotalPrice(price.multiply(order.getNum()));
        }
        map.put("price",order.getPrice());
        map.put("accessKey",api.getAccessKey());
        map.put("nonce",System.currentTimeMillis());
        String sign = sign(api, ORDER_URL, map);
        map.put("signData",sign);
        StringBuilder params = new StringBuilder();
        map.entrySet().forEach(entry -> params.append(entry.toString()).append("&"));
        String paramStr = params.substring(0, params.length() - 1);
        String res = HttpClientUtil.post(BASE_URL + ORDER_URL + "?" + paramStr);
        log.info("币夫下单返回信息: {}", res);
        JSONObject data = JSONObject.parseObject(res);
        if(data !=null ) {
            if (data.getBoolean("success")) {
                String outId = data.getJSONObject("data").getString("orderId");
                order.setOutId(outId);
                order.setStatus(BourseOrder.Status.PUBLISHED.value());
                //更新账户余额
//                accountInfo(api);
            } else {
                log.error("币夫下单失败：{}", res);
                String msg = data.getString("message");
                order.setRemark(msg);
                order.setStatus(BourseOrder.Status.PUBLISH_ERROR.value());
            }
        } else {
            log.error("币夫下单解析数据错误");
        }
    };

    /**
     * 币夫批量下单接口
     * @return
     */
    public static <T extends BourseOrder> void order (BourseApi api, List<T> orders) {
        if (CollectionUtils.isEmpty(orders)) {
            log.error("下单量异常");
            return;
        } if (orders.size() == 1) {
            order(api, orders.get(0));
        } if (orders.size() > 5) {
            log.error("最大下单量为5");
            return;
        }
        TreeMap<String, Object> map = new TreeMap<>();
        JSONArray array = new JSONArray();
        map.put("symbol",  BourseEnum.BITFOREX.getSymbolFormat().symbolFormat(orders.get(0).getBaseCurrency(), orders.get(0).getQuoteCurrency()));
        orders.forEach( order -> {
            JSONObject object = new JSONObject();
            object.put("price",order.getPrice());
            object.put("amount",order.getNum());
            object.put("tradeType",(order.getTradeType() == 1 || order.getTradeType()== 3) ? 1 : 2);
            array.add(object);
        });
        map.put("ordersData", array.toJSONString());
        map.put("accessKey",api.getAccessKey());
        map.put("nonce",System.currentTimeMillis());

        String sign = sign(api, BATCH_ORDER_URL, map);
        map.put("signData",sign);

        //处理特殊字符
        try {
            map.put("ordersData", URLEncoder.encode(array.toJSONString(), "UTF-8"));
        } catch (UnsupportedEncodingException e) {
            log.error("URL encoder 异常", e);
            throw new TransactionException("币夫下单失败");
        }
        log.info("array: {}", array.toJSONString());
        log.info("ordersData: {}", map.get("ordersData"));

        StringBuilder params = new StringBuilder();
        map.entrySet().forEach(entry -> params.append(entry.toString()).append("&"));
        String paramStr = params.substring(0, params.length() - 1);

        log.info("paramStr: {}", paramStr);
        String requestUrl = BASE_URL + BATCH_ORDER_URL + "?" + params;
        log.info("request url:{}", requestUrl);
        String res = HttpClientUtil.post(requestUrl);
        log.info("币夫下单返回信息: {}", res);
        if (null != res) {

            JSONObject jsonObject = JSON.parseObject(res);

            boolean success = jsonObject.getBoolean("success");

            String errorMsg = "";
            JSONArray dataArray = null;

            if (success) {
                dataArray = jsonObject.getJSONArray("data");
            } else {
                errorMsg = jsonObject.getString("message");
                log.error("币夫下单异常:{}",errorMsg);
            }

            for (int i = 0; i < orders.size(); i++) {
                T order = orders.get(i);
                if (success) {
                    JSONObject odDetail = dataArray.getJSONObject(i);
                    String outId = odDetail.getString("orderId");
                    if (outId.length() < 3) {
                        //该笔订单失败
                        order.setStatus(7);
                        order.setRemark(odDetail.getString("message"));
                    } else {
                        //成功订单
                        order.setStatus(1);
                        order.setOutId(outId);
                    }
                } else {
                    order.setStatus(7);
                    order.setRemark(errorMsg);
                }
            }
        } else {
            log.error("币夫批量下单请求无响应，未处理订单状态");
        }
    }

    /**
     * 币夫获取订单详情
     * @param api
     * @param order
     * @return
     */
    public static void orderInfo (BourseApi api, BourseOrder order) {
        TreeMap<String, Object> map = new TreeMap<>();
        map.put("symbol",  BourseEnum.BITFOREX.getSymbolFormat().symbolFormat(order.getBaseCurrency(), order.getQuoteCurrency()));
        map.put("orderId", order.getOutId());
        map.put("accessKey", api.getAccessKey());
        map.put("nonce", System.currentTimeMillis());

        String sign = sign(api, ORDER_INFO_URL, map);

        map.put("signData", sign);

        StringBuilder params = new StringBuilder();
        map.entrySet().forEach(entry -> params.append(entry.toString()).append("&"));

        String paramStr = params.substring(0, params.length() - 1);

        String requestUrl = BASE_URL + ORDER_INFO_URL + "?" + paramStr;
        String res = HttpClientUtil.post(requestUrl);
        log.info("币夫获取订单详情接口返回数据:{}", res);
        if (null != res) {
            JSONObject jsonObject = JSON.parseObject(res);
            if (jsonObject.getBoolean("success")) {
                JSONObject odDetail = jsonObject.getJSONObject("data");

                //币夫订单状态：0未成交、1部分成交、2全部成交、3部分成交已撤销、4全部撤销
                Integer orderState = odDetail.getInteger("orderState");
                if (1 == orderState) {
                    order.setDealTime(new Date());
                    order.setStatus(3);
                } else if (2 == orderState) {
                    order.setDealTime(new Date());
                    order.setStatus(6);
                } else if (3 == orderState) {
                    order.setStatus(4);
                } else if (4 == orderState) {
                    order.setStatus(5);
                }
                order.setDealNum(odDetail.getBigDecimal("dealAmount"));
                order.setAvgPrice(odDetail.getBigDecimal("avgPrice"));
                order.setFee(odDetail.getBigDecimal("tradeFee"));
            }
        }
    }

    /**
     * 取消订单
     * @param api
     * @param order
     * @return true表示撤单请求已成功执行，订单是否撤销成功需调用订单查询接口查询订单状态
     */
    public static boolean cancelOrder (BourseApi api, BourseOrder order) {
        boolean success = false;
        TreeMap<String, Object> map = new TreeMap<>();
        map.put("symbol", BourseEnum.BITFOREX.getSymbolFormat().symbolFormat(order.getBaseCurrency(), order.getQuoteCurrency()));
        map.put("orderId", order.getOutId());
        map.put("accessKey", api.getAccessKey());
        map.put("nonce", System.currentTimeMillis());

        String sign = sign(api, ORDER_CANCEL_PATH, map);

        map.put("signData", sign);

        StringBuilder params = new StringBuilder();
        map.entrySet().forEach(entry -> params.append(entry.toString()).append("&"));

        String paramStr = params.substring(0, params.length() - 1);

        String requestUrl = BASE_URL + ORDER_CANCEL_PATH + "?" + paramStr;
        log.error("request url:{}", requestUrl);
        String res = HttpClientUtil.post(requestUrl);
        log.error("币夫取消订单结果:{}", res);
        if (null != res) {
            JSONObject jsonObject = JSON.parseObject(res);
            if (jsonObject.getBoolean("success") && jsonObject.getBoolean("data")) {
                order.setStatus(2);
                success = true;
            }
        }
        return success;
    }

    public static String sign (BourseApi api, String url, TreeMap<String, Object> map) {
        final StringBuffer data = new StringBuffer();
        map.forEach( (key,value) -> {if (data.toString().equals("")) {
            data.append(key+"="+value);
        } else{
            data.append( "&" +key+"="+value);
        }});
        String sign = url + "?" + data.toString();
        String s = HmacUtil.bitforexSign(sign, api.getSecretKey());
//        map.put("signData",s);
//        final StringBuffer params = new StringBuffer();
//        map.forEach( (key,value) -> {if (params.toString().equals("")) {
//            params.append(key+"="+value);
//        } else{
//            params.append( "&" +key+"="+value);
//        }});
        return s;
    }

    /**
     * 深度信息获取
     * @param symbol
     * @return
     * @throws Exception
     */
    public static BoursePrice getBoursePrice (Symbol symbol) {
        String url = BASE_URL + DEPTH_PATH + "?symbol=" + BourseEnum.BITFOREX.getSymbolFormat().symbolFormat(symbol);
        String res = HttpClientUtil.get(url);
        log.error("币夫深度信息获取结果：{}", res);
        if (null != res) {
            JSONObject jsonObject = JSON.parseObject(res);
            if (jsonObject.getBoolean("success")) {
                BoursePrice price = new BoursePrice();
                JSONArray asks = jsonObject.getJSONObject("data").getJSONArray("asks");
                JSONArray bids = jsonObject.getJSONObject("data").getJSONArray("bids");

                JSONObject ask = asks.getJSONObject(0);
                JSONObject bid = bids.getJSONObject(0);

                price.setBuyPrice(bid.getBigDecimal("price"));
                price.setBuyNum(bid.getBigDecimal("amount"));

                price.setSellPrice(ask.getBigDecimal("price"));
                price.setSellNum(ask.getBigDecimal("amount"));
                return price;
            }
        }
        throw new TransactionException("获取币夫交易对" + symbol + "深度信息失败");
    }


}
